%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Exploiting multicore processors.

% We show how to use Mozart's transparent distribution
% layer to exploit multi-core processors.  All the
% examples have been run on a dual-core MacBook Pro
% with Mozart 1.4.0.

% In these examples, we use the remote module manager
% to execute any Oz code on a second process.  In a
% dual-core machine, this second process will run on
% the second core.  The Oz code is wrapped in a
% functor, so it can access resources (file system,
% operating system, etc.) in the second process.

% This code can easily be extended to handle failures,
% e.g., if the second process is killed.  This uses
% the fault streams as explained in the Mozart
% distribution tutorial.

% This code will also work if the second process is
% on another machine.  See the documentation of the
% Remote module for an explanation how this works.

% To understand how Mozart's distribution layer works,
% you can read Raphael Collet's Ph.D. thesis:
%   http://www.info.ucl.ac.be/~pvr/raphthesis.pdf

% Author: Peter Van Roy
% Date: Feb. 11, 2010


% Dummy computation for the examples
declare
fun {Fibo N}
   if N<2 then 1 else {Fibo N-1}+{Fibo N-2} end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% First step: create a remote module manager.

% Create a second process accessed through remote module manager R.
% {R apply(F ?M)} will execute functor F on the second process and
% return the module M.  The second process will execute on the
% second core of a dual core machine.
declare
R={New Remote.manager init(fork:sh)}

%%%% More than two cores.
% If running on a machine with more than two cores, you can
% run the above statement several times (with R1, R2, R3, etc.).
% Each time a new process and a remote module manager will be
% created.  You can extend the examples given below to use all
% the remote module managers at the same time to keep all cores busy.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Example executions

% Example execution on the second core
declare
functor F export x:X define X={Fibo 25} end
M={R apply(F $)}
{Browse M.x}

% Example execution that uses a resource on the second core
% (here it is the OS module; it could be e.g., the file system)
declare
functor F import OS export t:T define T={OS.time} end
M={R apply(F $)}
{Browse M.t}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% An abstraction to create remote threads

% Encapsulate the remote execution when no remote resources are used.
% P is an arbitrary nullary procedure executed in a remote thread.
% We're not interested in the remote module in this case.
declare
proc {RThread P}
   {R apply(functor define thread {P} end end _)}
end

% Execution that will keep both cores busy
declare F1 F2 in
{RThread proc {$} F1={Fibo 37} end}  % Remote thread
thread            F2={Fibo 37} end   % Local thread
{Browse F1#F2}

% Note that F1 is bound using *distributed lexical scoping*:
% F1 is bound on the remote process and because of lexical scoping
% it can be read on the the local process.  To make this work,
% Mozart's distribution layer uses a distributed binding protocol
% (it is actually a complete distributed unification protocol).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% An abstraction to do remote function calls

% Another way to encapsulate: take any one-argument function F
% and convert it into a remote function execution.
declare
fun {RemoteF F}
   fun {$ X}
      M={R apply(functor export res:Res define Res={F X} end $)}
   in
      M.res
   end
end

% Create a 'remote' Fibo function
declare
RemoteFibo={RemoteF Fibo}

% Another way to keep both cores busy
{Browse thread {RemoteFibo 37} end + thread {Fibo 37} end}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
